/* $Id: http.c 110 2007-04-17 03:18:21Z phrakt $ */
/*
 * Copyright (c) 2007 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <pwd.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <canopy/log.h>
#include <canopy/string.h>

#include <http/http.h>
#include <http/conn.h>
#include <http/vhost.h>

#include "http.h"
#include "acl.h"



struct http_acl_user {
	char                       *ac_username;
	TAILQ_ENTRY(http_acl_user)  ac_link;
};

struct http_acl_src {
	struct sockaddr_in          ac_sin;
	int                         ac_maskbits;
	TAILQ_ENTRY(http_acl_src)   ac_link;
};

struct http_acl_rule {
	int                          ac_id;
	int                          ac_op;
	int                          ac_flags;
	int                          ac_methods;	/* mask of methods */
	TAILQ_HEAD(, http_acl_src)   ac_sources;	/* message sources */
	TAILQ_HEAD(, http_acl_user)  ac_users;
	char                        *ac_path;

	TAILQ_ENTRY(http_acl_rule)   ac_link;
};


struct http_acl {
	TAILQ_HEAD(, http_acl_rule)  acl_rules;
	pthread_rwlock_t             acl_rwlock;
};




static struct http_acl_src* http_acl_newsrc  (const char *);


static int    http_acl_matchuser (struct http_acl_rule *, http_msg_t *);
static int    http_acl_matchsrc  (struct http_acl_rule *, http_msg_t *);
static int    http_acl_matchpath (struct http_acl_rule *, http_msg_t *);
static void   http_acl_freerule  (struct http_acl_rule *);


/*
 * http_acl_alloc()
 *
 * Allocate a new Access Control List structure and initialize it.
 * Returns a pointer to the new ACL structure on success, or NULL on failure.
 */
http_acl_t*
http_acl_alloc(void)
{
	int ret;
	http_acl_t *acl;

	if ((acl = malloc(sizeof(*acl))) == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to allocate ACL structure");
		return (NULL);
	}

	TAILQ_INIT(&acl->acl_rules);

	ret = pthread_rwlock_init(&acl->acl_rwlock, NULL);
	if (ret != 0) {
		cnp_log(CNP_LOG_ERR, "failed to initialize acl control "
		    "read-write lock");
		return (NULL);
	}

	return (acl);
}

/*
 * http_acl_free()
 *
 * Remove all of the remaining registered rules, and destroy the acl
 * read-write lock.
 */
void
http_acl_free(http_acl_t *acl)
{
	(void)http_acl_flush(acl);
	(void)pthread_rwlock_destroy(&acl->acl_rwlock);

	free(acl);
}

/*
 * http_acl_flush()
 *
 * Remove all of the registered rules.
 */
int
http_acl_flush(http_acl_t *acl)
{
	struct http_acl_rule *rule;

	if (pthread_rwlock_wrlock(&acl->acl_rwlock) != 0) {
	}

	while (!TAILQ_EMPTY(&acl->acl_rules)) {
		rule = TAILQ_FIRST(&acl->acl_rules);
		TAILQ_REMOVE(&acl->acl_rules, rule, ac_link);

		http_acl_freerule(rule);
	}

	(void)pthread_rwlock_unlock(&acl->acl_rwlock);
	return (0);
}

/*
 * http_acl_addrule()
 *
 * Add an access rule to the Access Control List <acl>.  The rule will perform
 * the operation <op>, which is either HTTP_ACL_ALLOW or HTTP_ACL_DENY.  All
 * other fields are optional and.  The <methods> argument is a bitmask of
 * the methods targeted by this rule.  The <sources> argument is an array
 * of pointers to NUL-terminated strings terminated by a NUL pointer.  Each
 * string in the array specifies a hostname, domain, address or network
 * mask of addresses targeted by this rule.
 *
 * Returns the new rule's ID on success, or -1 on failure.
 */
int
http_acl_addrule(http_acl_t *acl, enum http_acl_op op, int flags, int methods,
    const char *sources[], const char *path)
{
	int i;
	struct http_acl_rule *rule;
	struct http_acl_src *src;

	if ((op != HTTP_ACL_ALLOW) && (op != HTTP_ACL_DENY)) {
		cnp_log(CNP_LOG_ERR, "invalid acl rule operation");
		return (-1);
	}

	if ((rule = malloc(sizeof(*rule))) == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to allocate acl rule");
		return (-1);
	}

	rule->ac_op = op;
	if ((rule->ac_path = strdup(path)) == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to allocate acl rule path");
		free(rule);
		return (-1);
	}

	for (i = 0; sources[i] != NULL; i++) {
		src = http_acl_newsrc(sources[i]);
		if (src == NULL) {
			http_acl_freerule(rule);
			return (-1);
		}
	}

	return (0);
}

/*
 * http_acl_check()
 *
 * Check the acl restrictions imposed by the registered rules on the
 * request <req>.
 * Returns either HTTP_ACL_ALLOW or HTTP_ACL_DENY.
 */
enum http_acl_op
http_acl_check(http_acl_t *acl, http_msg_t *req)
{
	enum http_acl_op op = HTTP_ACL_DENY;
	struct http_acl_rule *rule;

	if (pthread_rwlock_rdlock(&acl->acl_rwlock) != 0) {
		cnp_log(CNP_LOG_ERR, "failed to acquire ACL read lock");
		return (op);
	}

	TAILQ_FOREACH(rule, &acl->acl_rules, ac_link) {
		if (!(rule->ac_methods & (1 << req->msg_req.req_method)))
			continue;

		if (!http_acl_matchuser(rule, req) ||
		    !http_acl_matchsrc(rule, req) ||
		    !http_acl_matchpath(rule, req))
			continue;
	}

	if (pthread_rwlock_unlock(&acl->acl_rwlock) != 0) {
		cnp_log(CNP_LOG_ERR, "failed to release ACL read lock");
		return (op);
	}

	return (op);
}

/*
 * http_acl_newsrc()
 *
 * Create a new source structure to represent the source string <source>.
 * The string can specify a source in four formats:
 *
 *	- a fully-qualified hostname  (ex. host.example.com)
 *	- a domain portion (ex. .example.com or .subnet.example.com)
 *	- a network address (ex. 192.168.24.4)
 *	- a network mask in CIDR notation (ex. 192.168.24/8)
 *
 * Returns a pointer to the new source structure on success, or NULL on
 * failure.
 */
static struct http_acl_src*
http_acl_newsrc(const char *source)
{
	struct http_acl_src *src;

	if ((src = malloc(sizeof(*src))) == NULL) {
		cnp_log(CNP_LOG_ERRNO, "failed to allocate new ACL source");
		return (NULL);
	}

	/* figure out what we're dealing with */

	if (source[0] == '.') {
		/* domain portion */
	}
	else if (strchr(source, '/') != NULL) {
		/* address mask using CIDR notation */
	}

	return (src);
}

/*
 * http_acl_matchuser()
 *
 */
static int
http_acl_matchuser(struct http_acl_rule *rule, http_msg_t *req)
{
	struct http_acl_user *user;

	/* matches any user */
	if (TAILQ_EMPTY(&rule->ac_users))
		return (1);

	TAILQ_FOREACH(user, &rule->ac_users, ac_link)
		if (strcmp(req->msg_req.req_auth->tok_user,
		    user->ac_username) == 0)
			return (1);

	return (0);
}

/*
 * http_acl_matchsrc()
 *
 */
static int
http_acl_matchsrc(struct http_acl_rule *rule, http_msg_t *req)
{

	return (0);
}

/*
 * http_acl_matchpath()
 *
 *
 */
static int
http_acl_matchpath(struct http_acl_rule *rule, http_msg_t *req)
{

	return (0);
}

/*
 * http_acl_freerule()
 *
 * Free an acl rule structure and associated resources.
 */
static void
http_acl_freerule(struct http_acl_rule *rule)
{
	struct http_acl_src *src;
	struct http_acl_user *user;

	while (!TAILQ_EMPTY(&rule->ac_sources)) {
		src = TAILQ_FIRST(&rule->ac_sources);
		TAILQ_REMOVE(&rule->ac_sources, src, ac_link);
		free(src);
	}

	while (!TAILQ_EMPTY(&rule->ac_users)) {
		user = TAILQ_FIRST(&rule->ac_users);
		TAILQ_REMOVE(&rule->ac_users, user, ac_link);
		free(user->ac_username);
		free(user);
	}

	if (rule->ac_path != NULL)
		free(rule->ac_path);

	free(rule);
}
